package org.kugua.strategy;

import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.text.MessageFormat;
import java.time.Instant;
import java.util.*;
import java.util.List;

import javax.imageio.ImageIO;

import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.kugua.context.CaptchaContext;
import org.kugua.dto.Captcha;
import org.kugua.enums.ColorEnum;
import org.kugua.enums.ImageType;
import org.kugua.font.FontProvider;
import org.kugua.font.provider.LocalFontProvider;
import org.kugua.effect.IEffect;
import org.kugua.effect.impl.AggressorLineEffect;
import org.kugua.processor.IPreProcessor;
import org.kugua.utils.RandomUtils;
import org.springframework.util.CollectionUtils;

/**
 * ParentCaptcha
 *
 * @author 苦瓜
 * @create 2024-08-26
 * @since 1.0.0
 */
@Slf4j
@Setter
@Getter
public abstract class ParentCaptchaStrategy implements CaptchaStrategy {

    public static final String CAPTCHA_IMAGE_FILE_NAME = "captcha";

    /**
     * 验证码默认宽度
     */
    public int width = 400;

    /**
     * 验证码默认高度
     */
    public int height = 200;

    /**
     * 验证码输出格式： 默认是PNG
     */
    private ImageType outImageType = ImageType.PNG;

    /**
     * 是否保存生成的文件
     */
    private boolean saveToLocal = false;

    /**
     * 字体提供器
     */
    protected FontProvider fontProvider = new LocalFontProvider();

    /**
     * 验证码效果
     */
     protected List<IEffect> effects = List.of(new AggressorLineEffect());

    /**
     * 验证码前置处理
     */
    private List<IPreProcessor> preProcess;

    /**
     * 验证码-生成
     *
     * @return Captcha
     */
    @Override
    public Captcha generate() {
        return generate(width, height);
    }

    /**
     * 验证码生成
     *
     * @return Captcha
     */
    @Override
    public Captcha generate(int width, int height) {
        return generate(width, height, null);
    }

    /**
     * 验证码生成
     *
     * @param width  宽度
     * @param height 高度
     * @param words  内容
     * @return Captcha
     */
    @Override
    public Captcha generate(int width, int height, String words) {
        return generate(width, height, words, null);
    }

    /**
     * 验证码生成
     *
     * @param width  宽度
     * @param height 高度
     * @param words  内容
     * @return Captcha
     */
    @Override
    public Captcha generate(int width, int height, String words, Font logical)  {
        long startTime = Instant.now().toEpochMilli();
        log.trace("generate start time: {}", startTime);
        BufferedImage bufferedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
        Graphics2D graphics = bufferedImage.createGraphics();
        Font font = Optional.ofNullable(logical).orElseGet(this::getFont);
        graphics.setFont(font);
        graphics.setBackground(Color.WHITE);
        graphics.fillRect(0, 0, width, height);
        // 抗锯齿
        graphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
        graphics.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
        graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        // 构建上下文
        String content = Optional.ofNullable(words).orElseGet(this::getContent);
        CaptchaContext context = new CaptchaContext(getType(), null, graphics, width,
                height, content);
        applyPreProcess(context);
        Captcha captcha = doFillContext(context);
        applyInterferenceEffects(context);
        graphics.dispose();
        String base64Image = toBase64(bufferedImage, outImageType);
        captcha.setImage(base64Image);
        captcha.setType(getType());
        long endTime = Instant.now().toEpochMilli();
        log.info("generate {} captcha end. time consuming:{}", context.getType() ,endTime - startTime);
        return captcha;
    }

    /**
     * 验证码是否有效
     *
     * @param source   原始值
     * @param target   目标值
     * @param accuracy 精度
     * @return boolean true：正确，否则false
     */
    @Override
    public boolean valid(List<String> source, List<String> target, Long accuracy) {
        if (CollectionUtils.isEmpty(target)) {
            return false;
        }
        return Optional.ofNullable(source).orElseGet(ArrayList::new)
                .parallelStream()
                .anyMatch(r -> r.equalsIgnoreCase(target.get(0)));
    }

    /**
     * 校验位置是否有效
     *
     * @param source 原始位置
     * @param target 目标位置
     * @param accuracy 精度
     * @return boolean
     */
    public static boolean validPosition(String source, String target, Long accuracy) {
        String[] sourcePosition = source.split(":", 2);
        int sourceX = Integer.parseInt(sourcePosition[0]);
        int sourceY = Integer.parseInt(sourcePosition[1]);
        String[] targetPosition = target.split(":", 2);
        int targetX = Integer.parseInt(targetPosition[0]);
        int targetY = Integer.parseInt(targetPosition[1]);
        return Math.abs(targetX - sourceX) <= accuracy && Math.abs(targetY - sourceY) <= accuracy;
    }

    /**
     * base64 编码输出
     *
     * @param bufferedImage 缓冲图片
     * @param outType 输出类型
     * @return 编码后的内容
     */
    public String toBase64(BufferedImage bufferedImage, ImageType outType) {
        try {
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            ImageIO.write(bufferedImage, outType.getType(), baos);
            if (saveToLocal) {
                externalSave(baos, outType);
            }
            return outType.getPrefix().concat(Base64.getEncoder().encodeToString(baos.toByteArray()));
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }


    /**
     * 外部保存
     *
     * @param baos 字节输出流
     */
    private void externalSave(ByteArrayOutputStream baos, ImageType outImageType) {
        String fileName = MessageFormat.format("{0}_{1}.{2}",
                CAPTCHA_IMAGE_FILE_NAME, String.valueOf(Instant.now().toEpochMilli()), outImageType.getType());
        try (FileOutputStream fos = new FileOutputStream(fileName)) {
            fos.write(baos.toByteArray());
            fos.flush();
        } catch (IOException e) {
           log.error("save to local failed.", e);
            throw new RuntimeException(e);
        }
    }

    /**
     * 添加干扰效果
     *
     * @param context 上下文
     */
    private void applyInterferenceEffects(CaptchaContext context) {
        if (!CollectionUtils.isEmpty(getEffects())) {
            getEffects().parallelStream().forEachOrdered(r -> r.effect(context));
        }
    }

    /**
     * 验证码内容填充
     *
     * @param context 内容
     * @return Captcha
     */
    public abstract Captcha doFillContext(CaptchaContext context);

    /**
     * 前置处理
     *
     * @param context 上下文信息
     */
    private void applyPreProcess(CaptchaContext context) {
        if (!CollectionUtils.isEmpty(preProcess)) {
            preProcess.parallelStream().forEachOrdered(r -> r.process(context));
        }
    }

    /**
     * 随机获取1个颜色
     *
     * @return Color
     */
    public Color randomColor() {
        List<ColorEnum> list = Arrays.stream(ColorEnum.values()).toList();
        Integer index = RandomUtils.random(list.size());
        ColorEnum colorEnum = list.get(index);
        return new Color(colorEnum.getR(), colorEnum.getG(), colorEnum.getB());
    }

    /**
     * 当前验证码类型
     *
     * @return String
     */
    public abstract String getType();

    /**
     * 默认验证码内容
     *
     * @return String[]
     */
    public String getContent() {
        return null;
    }

    /**
     * 字体
     *
     * @return Font 字体
     */
    public Font getFont() {
        return new Font("Logical", Font.BOLD, 20);
    }
}
